package no.awi.wpd.combat.service.impl;

import static no.awi.wpd.probability.ProbabilityDistribution.createBinomialProbabilityDistribution;
import static no.awi.wpd.probability.ProbabilityDistribution.ZERO;
import static no.awi.wpd.probability.WarhammerProbabilityFunctions.getAutohitWoundsPerAttack;
import static no.awi.wpd.probability.WarhammerProbabilityFunctions.getWoundsPerAttack;

import no.awi.wpd.combat.model.*;
import no.awi.wpd.combat.service.CombatCalculatorService;
import no.awi.wpd.probability.ProbabilityDistribution;
import no.awi.wpd.unit.model.Profile;
import no.awi.wpd.unit.model.Unit;

import java.util.List;
import java.util.SortedMap;

public class  CombatCalculatorServiceImpl implements CombatCalculatorService {

    public CombatResult calculateCombat(Combat combat) {
        SortedMap<InitiativeStep, List<AttackStep>> strikeOrder = combat.getStrikeOrder();
        for (List<AttackStep> attackSteps : strikeOrder.values()) {
            System.out.println(attackSteps.iterator().next().getInitiativeStep().getDescription());
            for (AttackStep attackStep : attackSteps) {
                calculateAttack(attackStep);
            }
            for (AttackStep attackStep : attackSteps) {
                attackStep.getAttackers().getAttackableOpponent().addWoundsTaken(attackStep.getWoundsCaused());
            }
        }
        ProbabilityDistribution staticResult = calculateStaticCombatBonus(combat);
        ProbabilityDistribution goodGuysWoundsTaken = combat.getGoodGuysContactZone().getWoundsTaken();
		ProbabilityDistribution badGuysWoundsTaken = combat.getBadGuysContactZone().getWoundsTaken();
		ProbabilityDistribution resultWithoutMusician = badGuysWoundsTaken.subtract(goodGuysWoundsTaken).add(staticResult);
		double drawProbability = resultWithoutMusician.getProbabilityByValue(0);
		int drawIndex = resultWithoutMusician.getIndexByValue(0);
		if (combat.getGoodGuys().isMusician() && !combat.getBadGuys().isMusician()) {
			resultWithoutMusician.addProbabilityByIndex(drawIndex + 1, drawProbability);
			resultWithoutMusician.setProbabilityByIndex(drawIndex, 0);
		} else if (!combat.getGoodGuys().isMusician() && combat.getBadGuys().isMusician()) {
			resultWithoutMusician.addProbabilityByIndex(drawIndex - 1, drawProbability);
			resultWithoutMusician.setProbabilityByIndex(drawIndex, 0);
		}
        CombatResult combatResult = new CombatResult();
        combatResult.setSide1WoundsCaused(badGuysWoundsTaken);
        combatResult.setSide2WoundsCaused(goodGuysWoundsTaken);
        combatResult.setResult(resultWithoutMusician);
        return combatResult;
    }

    private ProbabilityDistribution calculateNumberOfAttacks(AttackStep attackStep) {
        ProbabilityDistribution result = new ProbabilityDistribution(attackStep.getAttackers().getUnit()
                .getModelCount() * attackStep.getAttackerProfile().getAt() + 1, 0);
        if (attackStep.isAutohits()) {
            result = new ProbabilityDistribution(1, 0);
        }
        ContactZone contactZone = attackStep.getAttackers();
        ProbabilityDistribution woundsTaken = contactZone.getWoundsTaken();
        Unit unit = contactZone.getUnit();
        ProbabilityDistribution casualtiesDistribution = calculateCasualties(unit.getProfile(), woundsTaken);

        for (int i = 0; i < casualtiesDistribution.getSize(); i++) {
            Profile profile = attackStep.getAttackerProfile();
            int casualties = casualtiesDistribution.getValueByIndex(i);
            int modelsInBaseContact = Math.min(contactZone.getModelsInBaseContact(),
                    unit.getModelsInRank(casualties, 0));

            if (attackStep.isAutohits()) {
		        ProbabilityDistribution tmp = attackStep.getAutohits().multiplyValuesByConstant(modelsInBaseContact);
	            tmp.multiplyByConstant(casualtiesDistribution.getProbabilityByIndex(i));
                result = result.addAbsolutely(tmp);
                continue;
            }
            int supportAttacks = Math.min(contactZone.getModelsInBaseContact(),
                    unit.getModelsInRank(casualties, 1));
            if (unit.isHorde()) {
                supportAttacks += Math.min(contactZone.getModelsInBaseContact(),
                        unit.getModelsInRank(casualties, 2));
            }

            int supportAttacksPerModel = Math.min(profile.getAt(), profile.getType().maxSupportAttacks());
            int attacks = modelsInBaseContact * profile.getAt();
            attacks += supportAttacks * supportAttacksPerModel;
            result.setProbabilityByValue(attacks, casualtiesDistribution.getProbabilityByIndex(i));
        }
        result.trim();
	    if (!result.validateSum())
            throw new RuntimeException("Illegal number of attacks: " + result);
        return result;
    }

    void calculateAttack(AttackStep attackStep) {
        System.out.println(attackStep.getAttackerProfile().getName() + " attacks " +
                attackStep.getAttackers().getAttackableOpponent().getUnit().getName());

        ProbabilityDistribution result = ZERO();

        ProbabilityDistribution numberOfAttacks = calculateNumberOfAttacks(attackStep);
        System.out.println("Number of attacks: " + numberOfAttacks);
        Profile attackee = attackStep.getAttackers().getAttackableOpponent().getUnit().getProfile();
        Profile attackProfile = attackStep.getAttackerProfile();

        ProbabilityDistribution woundsPerAttack = attackStep.isAutohits() ?
                getAutohitWoundsPerAttack(attackProfile, attackee) :
                getWoundsPerAttack(attackProfile, attackee);

        for (int i = 0; i < numberOfAttacks.getSize(); i++) {
            int attacks = numberOfAttacks.getValueByIndex(i);
	        double probability = numberOfAttacks.getProbabilityByIndex(i);
	        if (probability == 0.0) {
		        continue;
	        }
            ProbabilityDistribution partial = woundsPerAttack.multiply(attacks);
            System.out.println(attacks + " attacks, wounds caused: " + partial);
            partial.multiplyByConstant(probability);
            System.out.println(attacks + " attacks, multiplied by probability (" + probability + "): " + partial);
            result = result.equals(ZERO()) ? partial : result.addAbsolutely(partial);
            System.out.println("Cumulative wounds: " + result);
        }
        if (!result.validateSum()) throw new IllegalStateException("Error calculating attack");
        attackStep.setWoundsCaused(result.withMaxValue(attackStep.getAttackers()
                .getAttackableOpponent().getNumberOfWounds()));
        System.out.println(result.confidenceInterval());
        System.out.println(result);
    }

    public ProbabilityDistribution calculateCasualties(Profile profile,
                                                       ProbabilityDistribution woundsTaken) {
        int maxCasualties = woundsTaken.getMaxValue() / profile.getWo() + 1;
        ProbabilityDistribution casualties = new ProbabilityDistribution(maxCasualties);
        double tempSum = 0;
        int currentCasualty = 0;
        for (int i = 0; i < woundsTaken.getSize(); i++) {

            int woundsTakenValue = woundsTaken.getValueByIndex(i);
            if (woundsTakenValue == 0 || woundsTakenValue % profile.getWo() != 0) {
                tempSum += woundsTaken.getProbabilityByIndex(i);
            } else {
                casualties.setProbabilityByIndex(currentCasualty++, tempSum);
                tempSum = woundsTaken.getProbabilityByIndex(i);
            }
        }
        if (tempSum != 0) {
            casualties.setProbabilityByIndex(maxCasualties - 1, tempSum);
        }
        return casualties;
    }

    public ProbabilityDistribution calculateStaticCombatBonus(Combat combat) {
    	ProbabilityDistribution badGuysWoundsTaken = combat.getBadGuysContactZone().getWoundsTaken();
    	ProbabilityDistribution goodGuysWoundsTaken = combat.getGoodGuysContactZone().getWoundsTaken();
        Unit badGuys = combat.getBadGuys();
		Unit goodGuys = combat.getGoodGuys();
		int possibilities = goodGuys.getStaticCombatBonus() + badGuys.getStaticCombatBonus() + 50;
    	int minValue = -badGuys.getStaticCombatBonus() - 20;
    	ProbabilityDistribution result = new ProbabilityDistribution(possibilities, minValue);
        int badGuysCasulties = -1;
    	for (int i = 0; i < badGuysWoundsTaken.getSize(); i++) {
            if (i % badGuys.getProfile().getWo() == 0)
                badGuysCasulties = Math.max(badGuysCasulties + 1, badGuys.getModelCount());
            int goodGuysCasulties = -1;
			for (int j = 0; j < goodGuysWoundsTaken.getSize(); j++) {
                if (i % goodGuys.getProfile().getWo() == 0)
                    goodGuysCasulties = Math.max(goodGuysCasulties + 1, goodGuys.getModelCount());
				int rankbonusGoodGuys = goodGuys.getRankBonus(goodGuysCasulties);
				int rankbonusBadGuys = badGuys.getRankBonus(badGuysCasulties);

				int currentResult = goodGuys.getStaticCombatBonus() + rankbonusGoodGuys
                        - badGuys.getStaticCombatBonus() - rankbonusBadGuys;
				result.addProbabilityByValue(currentResult, badGuysWoundsTaken.getProbabilityByIndex(i)
                        * goodGuysWoundsTaken.getProbabilityByIndex(j));
			}
		}
    	result.trim();
    	return result;
    }

    private int isSteadfast(Combat combat, int goodGuysCasulties, int badGuysCasulties) {
        return 0;
    }

}